package zhh.tangbao.system.aop;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import zhh.tangbao.system.annotation.Authority;
import zhh.tangbao.system.constant.AuthorityModel;
import zhh.tangbao.system.constant.CommonConstant;
import zhh.tangbao.system.exception.ReEntryException;
import zhh.tangbao.system.exception.ServiceException;
import zhh.tangbao.system.utils.HttpServletRequestUtil;
import zhh.tangbao.system.utils.UserHolderUtil;
import zhh.tangbao.user.cache.UserCache;
import zhh.tangbao.user.vo.UserVo;

import java.util.Set;

/**
 * @author 粥灰灰
 * @created 2022/11/4
 * @version 1.0
 * @Description 利用多态对AOP封装，目的是为了让其能够根据不同的实现类实现不同的AOP业务
 */

@Component
@Aspect
@Order(value = 1) // 数值越小优先级越高
public class AuthorityAop {

    @Autowired
    private UserHolderUtil userHolderUtil;

    @Autowired
    private UserCache userCache;

    @Pointcut("@annotation(zhh.tangbao.system.annotation.Authority)")
    public void authority() {
    }

    @Before(value = "@annotation(authority)")
    public void before(Authority authority) {
        String token = HttpServletRequestUtil.getRequestHeader(CommonConstant.TOKEN);
        String tk = HttpServletRequestUtil.getRequestHeader(CommonConstant.TOKEN_KEY);
        UserVo user = userCache.getLoginData(token, tk);
        // 需要登录的条件下用户没有在线状态，则视为未登录
        if (authority.login() && null == user) {
            throw new ReEntryException("未登录，请重新登录");
        }
        String currentLoginToken = userCache.getCurrentLoginToken();
        // 如果当前在线用户token不是空的则进行token校验
        if (StrUtil.isNotEmpty(currentLoginToken) && !currentLoginToken.equals(token)) {
            throw new ReEntryException("该账号在其他地方登录，您已被挤下线");
        }
        String[] authorities = authority.authorities();
        int authoritiesLength = authorities.length;
        // 不需要检测权限则直接放过
        if (AuthorityModel.NOT != authority.authorityModel() && authoritiesLength != 0) {
            // 权限验证
            Set<String> roleAuthorities = user.getAuthorities();
            // 需要权限验证但用户没有权限时就无需验证
            if (CollUtil.isEmpty(roleAuthorities)) {
                throw new ServiceException("用户没有任何权限，无法使用该功能，更改权限请联系管理员");
            }
            // 记录符合的权限个数
            int sameCount = 0;
            // 遍历接口权限，若权限出现在用户权限集合中则算作一次符合
            for (String a : authorities) {
                if (roleAuthorities.contains(a)) {
                    sameCount++;
                }
            }
            if (AuthorityModel.ALL == authority.authorityModel() && sameCount != authoritiesLength) {
                // 全检测需要符合权限的数量等于此接口标注的权限数量
                throw new ServiceException("权限不足，无法使用该功能，更改权限请联系管理员");
            } else if (AuthorityModel.CONTAIN == authority.authorityModel() && sameCount < 1) {
                // 包含检测只需要检查符合的权限个数是否大于一个，且在这之间已经将接口权限列表为0的情况排除
                throw new ServiceException("没有此权限，无法使用该功能，更改权限请联系管理员");
            }
        }
        // 因为有些接口在标注无需登录，使用的时候无论是否登录都可以使用，但这样会导致这些接口在用户已登录情况下使用后无法记录日志
        // 所以当请求带有token的时候不管如何都需要设置user
        // 如果是登录等特殊操作，会在登录页面直接设置当前用户
        userHolderUtil.setCurrentUser(user);
    }
}
